package hydrap2p.upload;

import hydrap2p.ServiceLocation;
import hydrap2p.helper.ServiceConnector;
import hydrap2p.logging.Level;
import hydrap2p.servicemanager.NameTaken;
import hydrap2p.speedlistener.SpeedListener;
import hydrap2p.upload.Uploader.Iface;

import java.io.IOException;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.thrift.TException;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;

public class Server implements Iface {

	private UploadProtocols protocols;
	private UploadQueue queue;
	private UploadListeners listeners;

	private TServer server;
	private Timer timer;
	private static final long pingerdelay = 10000;
	
	public static void main(String[] args) {
		try {
			new Server().run();
		} catch (Exception e) {
			System.err.println(e.getLocalizedMessage());
		}
	}
	
	public Server()
	{
		protocols=new UploadProtocols();
		listeners=new UploadListeners();
		queue=new UploadQueue(protocols,listeners);
	}

	public void run() throws IOException
	{
		String source="Library - Initialization";
		
		Random ports=new Random();
		int minport=1025;
		int maxport=50000;
		
		UploadLogging.log(Level.INFO,source,"Starting server");
		ServerSocket socket = new ServerSocket();
		int port=ports.nextInt(maxport-minport)+minport;
		int attempts=10;
		while (socket.getLocalPort()==-1 && attempts>0)
		{
			UploadLogging.log(Level.INFO,source,"Trying to bind to port "+port);
			try {
				socket.bind(new InetSocketAddress("localhost",port));
			}
			catch (Exception e) {
				// did not bind, try again
			}
			if (socket.getLocalPort()==-1)
				port=ports.nextInt(maxport-minport)+minport;
			attempts--;
		}
		if (socket.getLocalPort()==-1)
			throw(new BindException("Could not find a port to bind"));

		UploadLogging.log(Level.INFO,source,"Bound to port "+port);
		
		ServiceLocation here=new ServiceLocation();
		here.address="localhost";
		here.port=port;
		try {
			ServiceConnector.getServiceManager().setServiceLocation("Uploader", here);
		} catch (TException e) {
			throw(new BindException("Failed to tell Manager about my location, quitting"));
		} catch (NameTaken e) {
			throw(new BindException("Some other component has already claimed my service location: "+e.getLocation().address+":"+e.getLocation().port));
		} catch (NullPointerException e) {
			throw(new BindException("Failed to connect to Manager, quitting"));
		}
		
		timer = new Timer();
		timer.schedule(new PingTimer(), pingerdelay, pingerdelay);
		
		TServerTransport tsocket = new TServerSocket(socket);
		TThreadPoolServer.Args args = new TThreadPoolServer.Args(tsocket);
		args.processor(new Uploader.Processor(this));
		server = new TThreadPoolServer(args);
		server.serve();
	}
	
	private class PingTimer extends TimerTask {

		@Override
		public void run() {
			try {
				ServiceConnector.getServiceManager().ping();
			} catch (Exception e) {
				shutdown();
			}
		}
	}
	private void shutdown()
	{

		try {
			ServiceConnector.getServiceManager().delServiceLocation("Uploader");
		} catch (Exception e) {
		}
		server.stop();
		timer.cancel();
		System.exit(0);
	}
	
	
	@Override
	public boolean addUploadProtocol(ServiceLocation arg0, String arg1) {
		UploadProtocolIface protocol;
		try {
			protocol = new UploadProtocolThrift(arg0);
		} catch (TException e) {
			UploadLogging.log(Level.ERROR, "Upload", "Could not register upload protocol: "+e.getLocalizedMessage());
			return false;
		}
		return protocols.addUploadProtocol(arg1, protocol);
	}

	@Override
	public boolean removeUploadProtocol(ServiceLocation arg0) throws TException {
		return protocols.removeUploadProtocol(arg0);
	}

	@Override
	public boolean enqueueUpload(UploadQueued arg0) throws TException {
		return queue.enqueueUpload(arg0);
	}

	@Override
	public List<UploadQueued> getUploadQueue() throws TException {
		return queue.getUploadQueue();
	}

	@Override
	public boolean removeUpload(UploadQueued arg0) throws TException {
		return queue.removeUpload(arg0);
	}

	@Override
	public void subscribeListener(ServiceLocation arg0) throws TException {
		try {
			UploadListenerThrift listener=new UploadListenerThrift(arg0);
			listeners.subscribeListener(listener);
		}
		catch (TException e)
		{
			// failed to connect to the listener
			UploadLogging.log(Level.MISHAP, "Upload - Listeners", "Could not connect to an Upload Listener: "+e.getLocalizedMessage());
			throw e;
		}
	}

	@Override
	public void unsubscribeListener(ServiceLocation arg0) throws TException {
		listeners.unsubscribeListener(arg0);
	}

	@Override
	public void updateUpload(UploadQueued arg0) throws TException {
		queue.updateUpload(arg0);
	}

	@Override
	public void ping() throws TException {
		
	}

	@Override
	public List<String> supportedInterfaces() throws TException {
		List<String> ret=new LinkedList<String>();
		ret.add("Service");
		ret.add("Uploader");
		return ret;
	}

}
